package net.lulihu.pangolin.headless.common.container;

import lombok.extern.slf4j.Slf4j;
import net.lulihu.pangolin.headless.common.container.annotation.Autowired;
import net.lulihu.pangolin.headless.common.container.annotation.ComponentScan;
import net.lulihu.pangolin.headless.common.container.exception.AutowiredException;
import net.lulihu.pangolin.headless.common.container.exception.ComponentInitializationException;
import net.lulihu.pangolin.headless.common.container.exception.DependencyInjectionException;
import net.lulihu.pangolin.headless.common.container.templet.ApplicationLifeCycle;
import net.lulihu.pangolin.headless.common.container.templet.Initialization;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 应用启动
 */
@Slf4j
public class Application {

    // 应用初始化组件
    private static List<ApplicationLifeCycle> applicationLifeCycles = new ArrayList<>();


    static {
        // 应用关闭钩子
        Runtime.getRuntime().addShutdownHook(
                new Thread(() -> {
                    for (ApplicationLifeCycle applicationInitialize : applicationLifeCycles) {
                        try {
                            applicationInitialize.applicationShowdown();
                        } catch (Exception e) {
                            log.error("组件关闭时发生例外", e);
                        }
                    }
                }, "资源关闭线程"));
    }

    /**
     * 启动
     *
     * @param startClass 启动类
     * @param args       启动参数
     */
    public static void run(Class<?> startClass, String[] args) {
        if (log.isInfoEnabled()) log.info("应用启动ing");

        long start = System.currentTimeMillis();

        // 组件扫描
        if (!startClass.isAnnotationPresent(ComponentScan.class)) {
            throw new RuntimeException("请在启动类上使用@ComponentScan注解定义扫描路径");
        }

        // 扫描组件类
        Set<Class<?>> componentClasses = Scanner.scanningComponentClass(
                startClass.getAnnotation(ComponentScan.class));

        try {
            run(componentClasses, args);
        } catch (Exception e) {
            log.error("应用启动时发生例外", e);
            stop();
        }
        // 应用启动结束 不可修改
        applicationLifeCycles = Collections.unmodifiableList(applicationLifeCycles);

        long end = System.currentTimeMillis();

        if (log.isInfoEnabled()) log.info("应用启动耗时: {}/秒", ((double) (end - start)) / 1000);
    }

    /**
     * 应用停止
     */
    public static void stop() {
        System.exit(-1);
    }

    private static void run(Set<Class<?>> componentClassArray, String[] args) throws Exception {
        if (componentClassArray == null || componentClassArray.size() == 0) return;

        if (log.isDebugEnabled()) log.debug("开始加载组件ing");

        HashMap<Class<?>, Object> componentClassMap = new HashMap<>(componentClassArray.size());
        for (Class<?> componentClass : componentClassArray) {
            componentClassMap.put(componentClass, null);
        }

        if (log.isDebugEnabled()) log.debug("IOC容器启动ing，组件进行相互依赖注入");

        //容器 加载组件
        componentLoadCycle(componentClassMap);

        if (log.isDebugEnabled()) log.debug("IOC容器启动成功");

        // 容器启动成功后
        for (ApplicationLifeCycle applicationInitialize : applicationLifeCycles) {
            applicationInitialize.containerLoadSuccessAfter();
        }

        // 应用启动成功后
        for (ApplicationLifeCycle applicationInitialize : applicationLifeCycles) {
            applicationInitialize.applicationLoadSuccessAfter(args);
        }

        if (log.isDebugEnabled()) log.debug("应用启动成功");
    }

    private static void componentLoadCycle(Map<Class<?>, Object> componentClassMap) throws Exception {
        for (Class<?> componentClass : componentClassMap.keySet()) {
            componentLoadCycle(componentClassMap, componentClass);
        }
    }

    /**
     * 组件加载周期
     */
    private static Object componentLoadCycle(Map<Class<?>, Object> componentClassMap, Class<?> componentClass) throws Exception {
        // 存在Bean容器中直接返回
        if (BeanUtil.contains(componentClass)) return BeanUtil.getBean(componentClass);

        // 初始化
        Object component = initialize(componentClass);
        // 添加到容器中
        BeanUtil.addBean(component);

        // 依赖注入
        dependencyInjection(componentClassMap, component);
        // 执行 Initialization 模板方法
        if (component instanceof Initialization) {
            ((Initialization) component).after();
        }

        // 添加应用初始化组件
        if (component instanceof ApplicationLifeCycle) {
            applicationLifeCycles.add((ApplicationLifeCycle) component);
        }
        return component;
    }

    /**
     * 初始化类
     */
    private static Object initialize(Class<?> componentClass) {
        try {
            return componentClass.newInstance();
            // 类初始化例外
        } catch (InstantiationException | IllegalAccessException e) {
            throw new ComponentInitializationException(componentClass + "组件初始化发生例外", e);
        }
    }

    /**
     * 依赖注入
     */
    private static void dependencyInjection(Map<Class<?>, Object> componentClassMap, Object component) throws Exception {
        Class<?> componentClass = component.getClass();
        try {
            Field[] fieldArray = componentClass.getDeclaredFields();
            if (fieldArray.length > 0) {
                for (Field field : fieldArray) {
                    if (field.isAnnotationPresent(Autowired.class)) {
                        Object bean;

                        Class<?> fieldType = field.getType();
                        // 容器存在则注入
                        if (BeanUtil.contains(fieldType)) {
                            bean = BeanUtil.getBean(fieldType);
                            // 存在待加载的集合中 递归执行
                        } else if (componentClassMap.containsKey(fieldType)) {
                            bean = componentLoadCycle(componentClassMap, fieldType);
                        } else {
                            throw new AutowiredException(componentClass + "未能找到依赖属性:" + fieldType);
                        }

                        // 赋值
                        if (!field.isAccessible())
                            field.setAccessible(true);

                        field.set(component, bean);
                    }
                }
            }
        } catch (IllegalAccessException e) {
            throw new DependencyInjectionException("依赖属性注入发生例外", e);
        }
    }

}
